package com.atguigu.gmall.common.config;


import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.model.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

import static java.awt.SystemColor.text;

/**
 * ClassName: MQProducerAckConfig
 * Package: com.atguigu.gmall.common.config
 * Description:
 *
 * @Author 刘轩
 * @Create 2023-09-04 16:35
 * @Version 1.0
 */


/**
 * @Description 消息发送确认-确保生产者消息不丢失
 * <p>
 * ConfirmCallback  只确认消息是否正确到达 Exchange 中
 * ReturnCallback   消息没有正确到达队列时触发回调，如果正确到达队列不执行
 * <p>
 * 1. 如果消息没有到exchange,则confirm回调,ack=false
 * 2. 如果消息到达exchange,则confirm回调,ack=true
 * 3. exchange到queue成功,则不回调return
 * 4. exchange到queue失败,则回调return
 */
@Component
@Slf4j
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    // 初始化操作
    @PostConstruct
    private void init() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    /**
     * 只确认消息是否正确到达 Exchange 中,成功与否都会回调
     *
     * @param correlationData 相关数据  非消息本身业务数据
     * @param ack             应答结果
     * @param cause           如果发送消息到交换器失败，错误原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String s) {
        // 判断
        if (ack) {
            // 说明到达交换机
            log.info("说明到达交换机");
        } else {
            // 说明没有到达交换机
            log.info("说明没有到达交换机:\t" + text);
            // 调用重试方法
            retrySendMsg(correlationData);
        }
    }


    /**
     * 消息没有正确到达队列时触发回调，如果正确到达队列不执行
     *
     * @param message    消息对象
     * @param replyCode  应答码
     * @param replyText  应答提示信息
     * @param exchange   交换器
     * @param routingKey 路由键
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.error("消息路由queue失败，应答码={}，原因={}，交换机={}，路由键={}，消息={}",
                replyCode, replyText, exchange, routingKey, message.toString());

        // 消息没有到达队列时才执行
        //  只要系统执行到这个方法，就应该执行重复发送
        // 从缓存中获取
        // 获取缓存中的key
        //当路由队列失败 也需要重发
        //1.构建相关数据对象
        String redisKey = message.getMessageProperties().getHeader("spring_returned_message_correlation");
        // 根据key 来获取字符串
        String correlationDataStr = (String) redisTemplate.opsForValue().get(redisKey);
        // 需要将这个字符串转化为CorrelationData
        GmallCorrelationData gmallCorrelationData = JSON.parseObject(correlationDataStr, GmallCorrelationData.class);
        //  如果是延迟消息就不走重试业务逻辑.
        if (gmallCorrelationData.isDelay()) return;
        //2.调用消息重发方法
        this.retrySendMsg(gmallCorrelationData);
    }

    // 重发消息


    private void retrySendMsg(CorrelationData correlationData) {
        //  获取到重试次数
        GmallCorrelationData gmallCorrelationData = (GmallCorrelationData) correlationData;
        int retryCount = gmallCorrelationData.getRetryCount();
        //  判断 0->1->2->3
        if (retryCount >= 3) {
            System.out.println("次数已到" + retryCount);
            //  如果次数已到，还没有发送出去.
            log.info("次数已到！");
            //  将消息，写入消息记录表。。。。。 做持久化，然后才能人工审核处理！ insert into msg value()
        } else {
            //  发送消息
            System.out.println("次数" + retryCount);
            retryCount++;
            gmallCorrelationData.setRetryCount(retryCount);
            //  将这个最新数据，写回缓存.
            this.redisTemplate.opsForValue().set(gmallCorrelationData.getId(), JSON.toJSONString(gmallCorrelationData), 10, TimeUnit.MINUTES);
            //  判断发送消息的类型
            if (gmallCorrelationData.isDelay()) {
                //  延迟消息
                this.rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(), gmallCorrelationData.getRoutingKey(), gmallCorrelationData.getMessage(),
                        message -> {
                            //  设置延迟时间
                            message.getMessageProperties().setDelay(1000 * gmallCorrelationData.getDelayTime());
                            //  返回
                            return message;
                        }, gmallCorrelationData);
                //  不能RabbitService.sendDelayMsg();
            } else {
                //  发送普通消息
                this.rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(), gmallCorrelationData.getRoutingKey(), gmallCorrelationData.getMessage(), gmallCorrelationData);
                //  不能RabbitService.sendMsg();
            }
        }
    }
}